package com.example.resource.config;

import jakarta.servlet.http.HttpServletRequest;
import lombok.SneakyThrows;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
import org.springframework.security.authentication.ProviderManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtDecoders;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.JwtBearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.SecurityFilterChain;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

import static org.springframework.security.oauth2.core.authorization.OAuth2AuthorizationManagers.hasScope;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {

    private final OAuth2ResourceServerProperties properties;

    public SecurityConfig(OAuth2ResourceServerProperties properties) {
        this.properties = properties;
    }

    @Bean
    @SneakyThrows
    public SecurityFilterChain securityFilterChain(
            HttpSecurity http,
            AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver) {
        http
                .authorizeHttpRequests(authorize -> authorize
                        // 对于任何以 /messages/ 开头的 URL，以上要求使用 messages 的范围。
                        .requestMatchers("/contacts/**").access(hasScope("contacts"))
                        .requestMatchers("/messages/**").access(hasScope("messages"))
                        .anyRequest().authenticated()
                )
                // Spring Security 支持使用两种形式的 OAuth 2.0 Bearer Tokens 来保护端点： JWT 和 opaque tokens。
                .oauth2ResourceServer((oauth2) -> oauth2
                        // JWT 令牌的验证器
                        .jwt(Customizer.withDefaults())
                        // opaque tokens 的验证器
                        .opaqueToken((opaque) -> opaque
                                .introspectionClientCredentials(
                                        properties.getOpaquetoken().getClientId(),
                                        properties.getOpaquetoken().getClientSecret()
                                )
                                .introspectionUri(properties.getOpaquetoken().getIntrospectionUri())
                        )
                        // 多租户的认证管理器
                        .authenticationManagerResolver(authenticationManagerResolver)
                )
                // 不使用 Session 会话，而是使用无状态的 JWT 或 opaque tokens
                .sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                // 设置 CSRF 保护，防止跨站请求伪造攻击
                .csrf(Customizer.withDefaults())
                // 异常处理
                .exceptionHandling((exceptions) -> exceptions
                        .authenticationEntryPoint(new BearerTokenAuthenticationEntryPoint())
                        .accessDeniedHandler(new BearerTokenAccessDeniedHandler())
                );
        return http.build();
    }

    // JwtDecoder 用于将字符串令牌解码为经过验证的 Jwt 实例
    @Bean
    public JwtDecoder jwtDecoder(OAuth2ResourceServerProperties properties) {
        return JwtDecoders.fromIssuerLocation(properties.getJwt().getJwkSetUri());
    }


    @Bean
    AuthenticationManagerResolver<HttpServletRequest> multitenantAuthenticationManager (
            JwtDecoder jwtDecoder,
            OpaqueTokenIntrospector opaqueTokenIntrospector) {
        Map<String, AuthenticationManager> authenticationManagers = new HashMap<>();
        authenticationManagers.put("tenantOne", jwt(jwtDecoder));
        authenticationManagers.put("tenantTwo", opaque(opaqueTokenIntrospector));
        return (request) -> {
            String[] pathParts = request.getRequestURI().split("/");
            String tenantId = (pathParts.length > 0) ? pathParts[1] : null;
            // @formatter:off
            return Optional.ofNullable(tenantId)
                    .map(authenticationManagers::get)
                    .orElseThrow(() -> new IllegalArgumentException("unknown tenant"));
            // @formatter:on
        };
    }

    // 自定义 JwtAuthenticationProvider 用于验证 JWT 令牌
    private AuthenticationManager jwt(JwtDecoder jwtDecoder) {
        JwtAuthenticationProvider authenticationProvider = new JwtAuthenticationProvider(jwtDecoder);
        authenticationProvider.setJwtAuthenticationConverter(new JwtBearerTokenAuthenticationConverter());
        return new ProviderManager(authenticationProvider);
    }

    // 自定义 OpaqueTokenAuthenticationProvider 用于验证 opaque tokens
    private AuthenticationManager opaque(OpaqueTokenIntrospector introspectionClient) {
        return new ProviderManager(new OpaqueTokenAuthenticationProvider(introspectionClient));
    }

}

// 资源服务器配置参考 https://docs.springjava.cn/spring-security/reference/servlet/oauth2/resource-server/index.html